/**
 * LiveUpdater（数据上报器）
 *
 * 做什么：把“当前对战数据”发到后端接口，方便前端展示或平台消费。
 * 你需要提供：
 * - 比赛双方与上场名单（通常来自 LiveEvent 的配置数据）
 * - 可选比分与玩家的实时字段（如 score/hp）
 *
 * LiveEvent vs LiveUpdater：
 * - LiveEvent 只管“赛程配置”（什么时候打、谁上场、地图等），不算分、不算 KDA；
 * - LiveUpdater 只管“把你的实时数据发出去”。
 */

import type {
  Match,
  PlayerRef,
  LiveUpdatePlayer,
  LiveUpdatePayload,
  LiveUpdatePayloadArray,
  LiveUpdaterOptions,
  LiveUpdateHighlight,
  LiveDanmaku,
} from './types';

export default class LiveUpdater {
  private endpoint: string;
  private key: string;
  private headers: Record<string, string>;
  private maxRetries: number = 0;
  private retryDelayMs: number = 500;
  /** 提交最小间隔（毫秒） */
  private minIntervalMs: number = 10;
  /** 上次提交时间（毫秒时间戳） */
  private lastSubmitAtMs = 0;
  /** 已成功上报过的对战数据（按 startTime 缓存），用于作为默认值补全历史 */
  private historyByStart: Record<number, LiveUpdatePayload> = {};
  /** 本地暂存的对战数据（按 startTime 缓存），用于“累计历史 + 当前”一起提交 */
  private stagedByStart: Record<number, LiveUpdatePayload> = {};
  private fetcher: (
    url: string,
    init?: GameHttpFetchRequestOptions
  ) => Promise<{ json: () => Promise<unknown> }> = (url, init) =>
    // 复用与 LiveEvent 相同的 http.fetch 全局实现
    http.fetch(url, init);

  constructor(opts: LiveUpdaterOptions) {
    this.endpoint = opts.endpoint ?? 'https://api.box3lab.com/set_redis_value';
    this.key = opts.key;
    this.headers = {
      'Content-Type': 'application/json',
      ...(opts.headers ?? {}),
    };
    // 覆盖重试与退避配置
    if (typeof opts.maxRetries === 'number') {
      this.maxRetries = opts.maxRetries;
    }
    if (typeof opts.retryDelayMs === 'number') {
      this.retryDelayMs = opts.retryDelayMs;
    }
  }

  /**
   * 立刻发送一条弹幕，平台侧会展示该弹幕
   */
  async pushDanmakuNow(
    /** 比赛信息 */ match: Match,
    /** 弹幕内容 */ danmaku: LiveDanmaku
  ): Promise<boolean> {
    const base = this.stagedByStart[match.startTime] ||
      this.historyByStart[match.startTime] || {
        startTime: match.startTime,
        teamA: { score: 0, players: [] },
        teamB: { score: 0, players: [] },
      };
    //@ts-expect-error
    danmaku.ts = Date.now();
    const payload: LiveUpdatePayload = {
      ...base,
      danmaku,
    };
    this.stagedByStart[match.startTime] = payload;
    return this.pushMatchUpdate(payload);
  }

  /**
   * 判断后端返回是否“成功”。
   * 支持多种写法（大小写不敏感）：true、1、"true"、"1"、"ok"、"success"。
   */
  private static isSuccessCode(v: unknown): boolean {
    if (v === true) {
      return true;
    }
    if (v === 1) {
      return true;
    }
    if (typeof v === 'string') {
      const s = v.trim().toLowerCase();
      return s === 'true' || s === '1' || s === 'ok' || s === 'success';
    }
    return false;
  }

  /**
   * 推送对战数据（支持单场或多场）。
   * - 自动 JSON 序列化
   * - 自动重试（可配置次数与间隔）
   * - 返回 true 表示后端确认写入成功（兼容 code/success/ok 多种写法）
   */
  private async pushMatchUpdate(
    /** 比赛信息 */
    payload: LiveUpdatePayload | LiveUpdatePayloadArray
  ): Promise<boolean> {
    const requestBody = {
      key: this.key,
      value: JSON.stringify(payload),
      t: 12000,
    };

    let attempt = 0;
    let delay = this.retryDelayMs;
    // 重试循环
    while (true) {
      try {
        const res = await this.fetcher(this.endpoint, {
          method: 'POST',
          headers: this.headers,
          body: JSON.stringify(requestBody),
        });
        type PushResponse = {
          code?: unknown;
          success?: unknown;
          ok?: unknown;
          [k: string]: unknown;
        };
        const data = (await res.json()) as PushResponse;
        const successField = (data &&
          (data.code ?? data.success ?? data.ok)) as unknown;
        const ok = LiveUpdater.isSuccessCode(successField);
        if (!ok) {
          // 轻量告警：帮助排查后端返回格式与预期不一致的情况
          console.warn(
            '[LiveUpdater] push failed :',
            JSON.stringify({
              endpoint: this.endpoint,
              key: this.key,
              ttlSec: 12000,
            }),
            'response:',
            JSON.stringify(data)
          );
        }
        // 成功则更新本地历史缓存，供后续自动补全（历史+当前）使用
        if (ok) {
          this.updateHistoryFromPayload(payload);
        }
        return ok;
      } catch (err) {
        if (attempt >= this.maxRetries) {
          throw err;
        }
        await new Promise((r) => setTimeout(r, delay));
        attempt++;
        delay *= 2; // 指数退避
      }
    }
  }

  // ---- 工具方法：把赛程配置组装成上报数据（payload） ----
  /** 将成功上报的 payload 记录到 historyByStart */
  private updateHistoryFromPayload(
    payload: LiveUpdatePayload | LiveUpdatePayloadArray
  ): void {
    const list = Array.isArray(payload) ? payload : [payload];
    for (const p of list) {
      this.historyByStart[p.startTime] = p;
    }
  }
  /**
   * 把一场比赛（Match）变成上报 payload。
   * - scores：可选比分（不提供则默认 0）
   * - playerExtras：按玩家 id 注入扩展字段（例如 score/hp）
   * 备注：Match 通常来自 LiveEvent 的配置数据。
   */
  static buildPayloadFromMatch(
    /** 比赛信息 */
    match: Match,
    /** 比分 */
    scores?: { teamA?: number; teamB?: number },
    /** 玩家扩展字段 */
    playerExtras?: Record<number, Partial<LiveUpdatePlayer>>
  ): LiveUpdatePayload {
    const mapRef = (p: PlayerRef): LiveUpdatePlayer => ({
      id: p.id,
      ...(playerExtras?.[p.id] ?? {}),
    });
    return {
      startTime: match.startTime,
      teamA: {
        score: scores?.teamA ?? 0,
        players: match.teamA.players.map(mapRef),
      },
      teamB: {
        score: scores?.teamB ?? 0,
        players: match.teamB.players.map(mapRef),
      },
    };
  }

  /**
   * 将单场比赛加入“暂存队列”（staged），仅覆盖本次显式提供的字段，其余保持原值（若无原值则使用默认）。
   */
  private stageFromMatch(
    match: Match,
    scores?: { teamA?: number; teamB?: number },
    playerExtras?: Record<number, Partial<LiveUpdatePlayer>>
  ): void {
    const start = match.startTime;
    // 基础：已有暂存值 > 已有历史值 > 新建默认值
    const base =
      this.stagedByStart[start] ||
      this.historyByStart[start] ||
      LiveUpdater.buildPayloadFromMatch(match);

    // 覆盖比分（仅当显式提供）
    const nextScoreA = scores?.teamA ?? base.teamA.score ?? 0;
    const nextScoreB = scores?.teamB ?? base.teamB.score ?? 0;

    // 覆盖玩家扩展字段（仅当显式提供到某个玩家）
    const applyPlayerExtras = (
      sidePlayers: LiveUpdatePlayer[],
      extras?: Record<number, Partial<LiveUpdatePlayer>>
    ): LiveUpdatePlayer[] =>
      sidePlayers.map((pl) =>
        extras?.[pl.id] ? { ...pl, ...extras[pl.id], id: pl.id } : pl
      );

    const staged: LiveUpdatePayload = {
      startTime: start,
      teamA: {
        score: nextScoreA,
        players: applyPlayerExtras(base.teamA.players, playerExtras),
      },
      teamB: {
        score: nextScoreB,
        players: applyPlayerExtras(base.teamB.players, playerExtras),
      },
      highlight: base.highlight,
    };

    this.stagedByStart[start] = staged;
  }

  /**
   * 立刻上报单条高光。
   * 仅推送该场比赛的 payload，平台可据此放大 live_mini 并切镜。
   */
  async pushHighlightNow(
    /** 比赛信息 */ match: Match,
    /** 高光内容 */
    highlight: LiveUpdateHighlight
  ): Promise<boolean> {
    const base = this.stagedByStart[match.startTime] ||
      this.historyByStart[match.startTime] || {
        startTime: match.startTime,
        teamA: { score: 0, players: [] },
        teamB: { score: 0, players: [] },
        highlight: {},
      };
    //@ts-expect-error
    highlight.ts = Date.now();
    const payload: LiveUpdatePayload = {
      ...base,
      highlight: { ...(base.highlight ?? {}), ...highlight },
    };
    // 更新本地 staged，保持与即时上报一致
    this.stagedByStart[match.startTime] = payload;
    // 直接推送单场
    return this.pushMatchUpdate(payload);
  }

  /**
   * 立刻停止当前高光：清空该场比赛的 highlight 并立即上报。
   */
  async stopHighlightNow(match: Match): Promise<boolean> {
    const base = this.stagedByStart[match.startTime] ||
      this.historyByStart[match.startTime] || {
        startTime: match.startTime,
        teamA: { score: 0, players: [] },
        teamB: { score: 0, players: [] },
      };
    const payload: LiveUpdatePayload = {
      ...base,
      // 使用空对象表示“无高光”，便于平台清空并复位UI
      highlight: {
        //@ts-expect-error
        ts: 0,
        reason: '',
      },
    };
    this.stagedByStart[match.startTime] = payload;
    return this.pushMatchUpdate(payload);
  }

  /**
   * 提交所有“暂存队列”的对战数据（历史 + 当前）一起到后端。
   * 成功后会用返回值规则更新本地 history 缓存。
   */
  private async submitStaged(): Promise<boolean> {
    const list = Object.values(this.stagedByStart).sort(
      (a, b) => a.startTime - b.startTime
    );
    if (list.length === 0) {
      return true;
    }
    // 节流：确保两次提交至少间隔 minIntervalMs
    const now = Date.now();
    const elapsed = now - this.lastSubmitAtMs;
    if (this.lastSubmitAtMs && elapsed < this.minIntervalMs) {
      await new Promise((r) => setTimeout(r, this.minIntervalMs - elapsed));
    }
    this.lastSubmitAtMs = Date.now();
    const ok = await this.pushMatchUpdate(list);
    if (ok) {
      this.updateHistoryFromPayload(list);
    }
    return ok;
  }

  /**
   * 从单场比赛直接推送。
   */
  async pushFromMatch(
    /** 比赛信息 */ match: Match,
    /** 比分 */ scores: { teamA: number; teamB: number },
    /** 玩家扩展字段 */ playerExtras?: Record<number, Partial<LiveUpdatePlayer>>
  ): Promise<boolean> {
    this.stageFromMatch(match, scores, playerExtras);
    return this.submitStaged();
  }
}
